﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace PnFramework.Unity
{
    /*
     大家好 我是猫叔 今天来给大家做个计时器系统。
     计时器有啥用呢，比如说大家在游戏中应该经常会见到一些计时任务，
     比如一些倒计时 子弹装填 技能冷却什么的！
     今天给你们封装一个比较好用的计时器系统 首先我们需要实现一个计时器
    */
    /// <summary>
    /// 基础计时器
    /// </summary>
    public class Timer
    {
        //计时器需要什么呢 首先需要一个计时完成的委托 也就是计时完成时所做的事情      
        private Action OnFinished;
        //还需要根据外部提供的延迟时间计算完成的时间点
        private float mFinishTime;
        private float mDelayTime;
        //接着还需要知道是单次计时还是循环计时 比如技能冷却属于单次计时 每隔一段时间回血是循环计时
        private bool mLoop;
        //最后需要提供一个计时器是否完成的标识给外部确认状态
        private bool mIsFinish;
        public bool IsFinish => mIsFinish;
        //我们可以提供一个 开始 和 结束 方法给外部方便手动操作 这里就是一些赋值和简单计算 不过多讲
        /// <summary>
        /// 开始计时
        /// </summary>
        public void Start(Action onFinished, float delayTime, bool isLoop)
        {
            OnFinished = onFinished;
            mFinishTime = Time.time + delayTime;
            mDelayTime = delayTime;
            mLoop = isLoop;
            mIsFinish = false;
        }
        /// <summary>
        /// 提供给外部手动停止的方法
        /// </summary>
        public void Stop() => mIsFinish = true;
        /// <summary>
        /// 更新计时器
        /// </summary>
        public void Update()
        {
            //这里是一个计时器的更新方法 实现很简单 如果计时器没有处于开始状态 就不执行计时操作
            if (mIsFinish) return;
            //如果当前的时间 还小于完成时间 就继续计时
            if (Time.time < mFinishTime) return;
            //如果计时器完成计时 看一下计时器是否为循环状态 如果不是循环状态 就关闭计时器
            if (!mLoop) Stop();
            //如果是循环状态 就重置完成时间
            else mFinishTime = Time.time + mDelayTime;
            //最后触发计时完成事件
            OnFinished?.Invoke();
        }
    }
    /// <summary>
    /// 计时器系统
    /// </summary>
    public class TimerMgr : Singleton<TimerMgr>
    {
        //来到我们的计时器系统 因为我希望计时器能够被复用 所以我实现了一个简易的计时器对象池 
        //大致思路是 使用列表去存储已激活的计时器 并更新状态 使用一个队列来存储未激活的计时器
        //方便新的计时任务使用 可以对外提供一个添加计时器的方法
        private List<Timer> mUpdateList = new List<Timer>();
        private Queue<Timer> mAvailableQueue = new Queue<Timer>();

        public TimerMgr() => EventMgr.Instance.Register(MonoKit.OnUpdate,Update);

        /// <summary>
        /// 添加计时器
        /// </summary>
        public Timer AddTimer(Action onFinished, float delayTime, bool isLoop = false)
        {
            //首先看看可用队列中是否有闲置的计时器 如果没有就初始化一个 如果有就拿出来用
            var timer = mAvailableQueue.Count == 0 ? new Timer() : mAvailableQueue.Dequeue();
            //这里是调用开始计时方法
            timer.Start(onFinished, delayTime, isLoop);
            //加入到更新列表参与更新
            mUpdateList.Add(timer);
            //这里可用返回当前计时器对象用于 手动停止或做一些操作
            return timer;
        }
        /// <summary>
        /// 更新计时器
        /// </summary>
        private void Update()
        {
            //计时器系统更新方法也不是很难 首先判断是否有计时任务需要更新
            if (mUpdateList.Count == 0) return;
            //如果有就遍历列表 判断列表中对象是否处于完成状态
            for (int i = mUpdateList.Count - 1; i >= 0; i--)
            {
                //如果当前计时任务已经完成 就从更新列表中删除并添加到可用列表且不再执行更新
                if (mUpdateList[i].IsFinish)
                {
                    mAvailableQueue.Enqueue(mUpdateList[i]);
                    mUpdateList.RemoveAt(i);
                    continue;
                }
                mUpdateList[i].Update();
            }
        }
        //好了 今天的内容就是这样 下一期 我们将使用计时器系统完善和优化我们的弹幕系统
        //记得一键三连 么么哒
    }
}